package com.io.nio2;

import com.io.util.CalculatorUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * @author hjj
 * @create 2019-11-24 18:46
 */
public class NIOServerHandler implements Runnable {
    private Selector selector;                // 多路复用选择器
    private ServerSocketChannel serverChannel;  // 服务器通道
    private volatile boolean started;            // 服务器状态，避免服务器关闭后还继续循环
    private final int BUFFER_SIZE = 1024;        // 缓冲区大小

    public NIOServerHandler(int port) {
        try {
            selector = Selector.open();
            serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            serverChannel.socket().bind(new InetSocketAddress(port));
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            started = true;
            System.out.println("NIO server start >>> port: " + port);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    public void stop() {
        started = false;
    }


    @Override
    public void run() {
        while (started) {
            try {
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                SelectionKey key;
                while (iterator.hasNext()) {
                    key = iterator.next();
                    iterator.remove();
                    handleInput(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Throwable t) {
                t.printStackTrace();
            }
        }
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void handleInput(SelectionKey key) throws IOException {
        if (!key.isValid()) return;
        if (key.isAcceptable()) accept(key);
        if (key.isReadable()) read(key);
    }

    public void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    public void read(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
        int readBytes = socketChannel.read(byteBuffer);
        if (readBytes > 0) {
            byteBuffer.flip();
            byte[] bytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(bytes);
            String message = new String(bytes, "UTF-8");
            System.out.println("NIO server received: " + message);
            message = CalculatorUtil.cal(message).toString();
            send(socketChannel, message);
            System.out.println("NIO server send: " + message);
        } else if (readBytes < 0) {
            key.cancel();
            socketChannel.close();
        }
    }

    public void send(SocketChannel socketChannel, String message) throws IOException {
        socketChannel.register(selector, SelectionKey.OP_WRITE);
        byte[] bytes = message.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        writeBuffer.put(bytes);
        writeBuffer.flip();
        socketChannel.write(writeBuffer);
    }
}
